/*
 * CC3Material.m
 *
 * cocos3d 0.6.1
 * Author: Bill Hollings
 * Copyright (c) 2010-2011 The Brenwill Workshop Ltd. All rights reserved.
 * http://www.brenwill.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * http://en.wikipedia.org/wiki/MIT_License
 * 
 * See header file CC3Material.h for full API documentation.
 */

#include "CC3Material.h"
#include "CC3OpenGLES11Engine.h"

CC3Material::CC3Material() :
	textureOverlays(NULL)
{
}

CC3Material::~CC3Material()
{
	texture->release();
	if (textureOverlays) 
	{
		textureOverlays->release();
	}
}

bool CC3Material::getShouldUseLighting()
{
	return shouldUseLighting;
}

void CC3Material::setShouldUseLighting(bool newValue)
{
	shouldUseLighting = newValue;
}

cocos2d::ccColor4F CC3Material::getAmbientColor()
{
	return ambientColor;
}

void CC3Material::setAmbientColor(cocos2d::ccColor4F newValue)
{
	ambientColor = newValue;
	checkIsOpaque();
}

void CC3Material::checkIsOpaque()
{
	isOpaque = (blendFunc.src == GL_ONE && blendFunc.dst == GL_ZERO);
}

cocos2d::ccColor4F CC3Material::getDiffuseColor()
{
	return diffuseColor;
}

void CC3Material::setDiffuseColor(cocos2d::ccColor4F newValue)
{
	diffuseColor = newValue;
	checkIsOpaque();
}

cocos2d::ccColor4F CC3Material::getSpecularColor()
{
	return specularColor;
}

void CC3Material::setSpecularColor(cocos2d::ccColor4F newValue)
{
	specularColor = newValue;
	checkIsOpaque();
}

cocos2d::ccColor4F CC3Material::getEmissionColor()
{
	return emissionColor;
}

void CC3Material::setEmissionColor(cocos2d::ccColor4F newValue)
{
	emissionColor = newValue;
	checkIsOpaque();
}

GLfloat CC3Material::getShininess()
{
	return shininess;
}

void CC3Material::setShininess(GLfloat newValue)
{
	shininess = CLAMP(newValue, 0.0, kCC3MaximumMaterialShininess);		// clamp to allowed range
}

GLenum CC3Material::getSourceBlend()
{
	return blendFunc.src;
}

void CC3Material::setSourceBlend(GLenum aBlend)
{
	blendFunc.src = aBlend;
	checkIsOpaque();
}

GLenum CC3Material::getDestinationBlend()
{
	return blendFunc.dst;
}

void CC3Material::setDestinationBlend(GLenum aBlend)
{
	blendFunc.dst = aBlend;
	checkIsOpaque();
}

bool CC3Material::getIsOpaque()
{
	return isOpaque;
}

void CC3Material::setIsOpaque(bool opaque)
{
	if (opaque) 
	{
		// If we're forcing full opacity, set no alpha blending
		blendFunc.src = GL_ONE;
		blendFunc.dst = GL_ZERO;
	} 
	else 
	{
		// Enable alpha blending. Set destination blend to (1-SRC_ALPHA).
		// Set source blend to SRC_ALPHA unless texture has pre-multiplied alpha AND
		// material is at full opacity. If material is not at full opacity, we must
		// enable source alpha blending even if texture has pre-multiplied alpha.
		bool texHasPreMultAlpha = texture && texture->getHasPremultipliedAlpha();
		blendFunc.src = (texHasPreMultAlpha && getOpacity() == 255) ? GL_ONE : GL_SRC_ALPHA;
		blendFunc.dst = GL_ONE_MINUS_SRC_ALPHA;
	}
	
	checkIsOpaque();
}

void CC3Material::setColor(cocos2d::ccColor3B color)
{
	GLfloat rf = CCColorFloatFromByte(color.r);
	GLfloat gf = CCColorFloatFromByte(color.g);
	GLfloat bf = CCColorFloatFromByte(color.b);
	
	ambientColor.r = rf;
	ambientColor.g = gf;
	ambientColor.b = bf;
	
	diffuseColor.r = rf;
	diffuseColor.g = gf;
	diffuseColor.b = bf;
}

cocos2d::ccColor3B CC3Material::getColor(void)
{
	return CCC3BFromCCC4F(diffuseColor);
}

GLubyte CC3Material::getOpacity(void)
{
	return CCColorByteFromFloat(diffuseColor.a);
}

void CC3Material::setOpacity(GLubyte opacity)
{
	GLfloat af = CCColorFloatFromByte(opacity);
	ambientColor.a = af;
	diffuseColor.a = af;
	specularColor.a = af;
	emissionColor.a = af;
	
	// As a convenience, if we're trying to reduce opacity, make sure the isOpaque
	// flag is compatible with that. We do NOT force it the other way, because the
	// texture may contain translucency, even if the material colors are fully opaque.
	if (opacity < 255) 
	{
		setIsOpaque(false);
	}
}

void CC3Material::setBlendFunc(cocos2d::ccBlendFunc aBlendFunc)
{
	blendFunc = aBlendFunc;
	checkIsOpaque();
}

cocos2d::ccBlendFunc CC3Material::getBlendFunc(void)
{
	return blendFunc;
}

static cocos2d::ccBlendFunc defaultBlendFunc = {GL_ONE, GL_ZERO};

cocos2d::ccBlendFunc CC3Material::getDefaultBlendFunc()
{
	return defaultBlendFunc;
}

void CC3Material::setDefaultBlendFunc(cocos2d::ccBlendFunc aBlendFunc)
{
	defaultBlendFunc = aBlendFunc;
}

CC3Texture* CC3Material::getTexture()
{
	return texture;
}

void CC3Material::setTexture(CC3Texture* newValue)
{
	CC3Texture* oldTexture = texture;
	texture = newValue;
	texture->retain();
	if (oldTexture) 
	{
		oldTexture->release();
	}
}

cocos2d::CCArray* CC3Material::getTextureOverlays()
{
	return textureOverlays;
}

void CC3Material::addTexture(CC3Texture* aTexture)
{
	//LogTrace(@"Adding %@ to %@", aTexture, self);
	if (aTexture) 
	{
		if (!texture) 
		{
			setTexture(aTexture);
		} 
		else 
		{
			if(!textureOverlays) 
			{
				textureOverlays = cocos2d::CCArray::array();
				textureOverlays->retain();
			}
			
			GLint maxTexUnits = CC3OpenGLES11Engine::engine()->getPlatform()->getMaxTextureUnits()->getValue();
			if (getTextureCount() < maxTexUnits) 
			{
				textureOverlays->addObject(aTexture);
			} 
			else 
			{
				//LogInfo(@"Attempt to add texture %@ to %@ ignored because platform supports only %i texture units.",
//						aTexture, self, maxTexUnits);
			}
		}
	}
}

GLuint CC3Material::getTextureCount()
{
	return (textureOverlays ? textureOverlays->count() : 0) + (texture ? 1 : 0);
}

bool CC3Material::getHasBumpMap()
{
	// Check the first texture.
	if (texture && texture->getIsBumpMap()) 
	{
		return true;
	}
	
	// Then check in the overlays array
	if (textureOverlays) 
	{
		CCObject* pObject = NULL;
		CCARRAY_FOREACH(textureOverlays, pObject)
		{
			CC3Texture* pTexture = (CC3Texture*)pObject;
			if (pTexture->getIsBumpMap()) 
			{
				return true;
			}
		}
	}
	
	return false;
}

CC3Vector CC3Material::getLightDirection()
{
	// Check the first texture.
	if (texture && texture->getIsBumpMap()) 
	{
		return texture->getLightDirection();
	}
	
	// Then check in the overlays array
	if (textureOverlays) 
	{
		CCObject* pObject = NULL;
		CCARRAY_FOREACH(textureOverlays, pObject)
		{
			CC3Texture* pTexture = (CC3Texture*)pObject;
			if (pTexture->getIsBumpMap())
			{
				return pTexture->getLightDirection();
			}
		}
	}
	return kCC3VectorZero;
}

void CC3Material::setLightDirection(CC3Vector aDirection)
{
	// Set the first texture.
	texture->setLightDirection(aDirection);
	// Then check in the overlays array
	if (textureOverlays) 
	{
		CCObject* pObject = NULL;
		
		CCARRAY_FOREACH(textureOverlays, pObject)
		{
			CC3Texture* pTexture = (CC3Texture*)pObject;
			pTexture->setLightDirection(aDirection);
		}
	}
}

void CC3Material::removeTexture(CC3Texture* aTexture)
{
	//LogTrace(@"Removing %@ from %@", aTexture, self);
	
	if (texture == aTexture) 
	{
		setTexture(NULL);
	} 
	else 
	{
		if (textureOverlays && aTexture) 
		{
			textureOverlays->removeObject((CCObject*)aTexture);
			if (textureOverlays->count() == 0) 
			{
				textureOverlays->release();
				textureOverlays = NULL;
			}
		}
	}
}

void CC3Material::removeAllTextures()
{
	// Remove the first texture
	removeTexture(texture);
	
	// Remove the overlay textures
	if (textureOverlays) 
	{
		CCObject* pObject = NULL;
		CCARRAY_FOREACH(textureOverlays, pObject)
		{
			CC3Texture* pTexture = (CC3Texture*)pObject;
			
			removeTexture(pTexture);
		}
	}
}

CC3Texture* CC3Material::getTextureNamed(const char* aName)
{
	const char* tcName;
	
	// First check if the first texture is the one
	if (texture) 
	{
		tcName = texture->getName().c_str();
		if (strcmp(tcName, aName) == 0 || (!tcName && !aName)) 
		{		// Name equal or both nil.
			return texture;
		}
	}
	// Then look for it in the overlays array
	if (textureOverlays) 
	{
		CCObject* pObject = NULL;
		CCARRAY_FOREACH(textureOverlays, pObject)
		{
			CC3Texture* pTexture = (CC3Texture*)pObject;
			tcName = pTexture->getName().c_str();
			
			if (strcmp(tcName, aName) == 0 || (!tcName && !aName)) 
			{		// Name equal or both nil.
				return texture;
			}
		}
	}
	return NULL;
}

CC3Texture* CC3Material::textureForTextureUnit(GLuint texUnit)
{
	// If first texture unit, return texture property, otherwise retrieve from overlay array
	if (texUnit == 0) 
	{
		return texture;
	} 
	else 
	{
		return (CC3Texture*)(textureOverlays->objectAtIndex((texUnit - 1)));
	}
}

void CC3Material::setTexture(CC3Texture* aTexture, GLuint texUnit)
{
	//cocos2d::CCAssert(aTexture, "Overlay texture cannot be nil");
	if (texUnit == 0) 
	{
		setTexture(aTexture);
	} 
	else if (texUnit < getTextureCount()) 
	{
		//getTextureOverlays()->replaceObjectAtIndex((texUnit - 1), aTexture);
		getTextureOverlays()->insertObject((cocos2d::CCObject*)aTexture, (texUnit - 1));
		getTextureOverlays()->removeObjectAtIndex(texUnit);
	} 
	else 
	{
		addTexture(aTexture);
	}
}

bool CC3Material::initWithTag(GLuint aTag, const char* aName)
{
	if ( CC3Identifiable::initWithTag(aTag, aName) ) 
	{
		texture = NULL;
		textureOverlays = NULL;
		ambientColor = kCC3DefaultMaterialColorAmbient;
		diffuseColor = kCC3DefaultMaterialColorDiffuse;
		specularColor = kCC3DefaultMaterialColorSpecular;
		emissionColor = kCC3DefaultMaterialColorEmission;
		shininess = kCC3DefaultMaterialShininess;
		blendFunc = getDefaultBlendFunc();
		shouldUseLighting = true;
		checkIsOpaque();
	}
	
	return true;
}

CC3Material* CC3Material::material()
{
	CC3Material* pMaterial = new CC3Material;
	if (pMaterial) 
	{
		pMaterial->autorelease();
	}
	
	return pMaterial;
}

CC3Material* CC3Material::materialWithTag(GLuint aTag)
{
	CC3Material* pMaterial = new CC3Material;
	if (pMaterial) 
	{
		pMaterial->initWithTag(aTag, pMaterial->getName().c_str());
		pMaterial->autorelease();
	}
	
	return pMaterial;
}

CC3Material* CC3Material::materialWithName(const char* aName)
{
	CC3Material* pMaterial = new CC3Material;
	if (pMaterial) 
	{
		pMaterial->initWithName(aName);
		pMaterial->autorelease();
	}
	
	return pMaterial;
}

CC3Material* CC3Material::materialWithTag(GLuint aTag, const char* aName)
{
	CC3Material* pMaterial = new CC3Material;
	if (pMaterial) 
	{
		pMaterial->initWithTag(aTag, aName);
		pMaterial->autorelease();
	}
	
	return pMaterial;
}

CC3Material* CC3Material::shiny()
{
	CC3Material* mat = material();
	if (mat) 
	{
		mat->setSpecularColor(kCCC4FWhite);
		mat->setShininess(75.0f);
	}
	
	return mat;
}

CC3Material* CC3Material::shinyWhite()
{
	CC3Material* mat = shiny();
	if (mat) 
	{
		mat->setDiffuseColor(kCCC4FWhite);
	}
	
	return mat;
}

void CC3Material::drawWithVisitor(CC3NodeDrawingVisitor* visitor)
{
	if (switchingMaterial()) 
	{
		applyBlend();
		applyColors();
		drawTexturesWithVisitor(visitor);
	} 
	else 
	{
		//LogTrace(@"Reusing currently bound %@", self);
	}
}

static GLuint currentMaterialTag = 0;

void CC3Material::resetSwitching()
{
	currentMaterialTag = 0;
}

bool CC3Material::switchingMaterial()
{
	bool shouldSwitch = (currentMaterialTag != getTag());
	currentMaterialTag = getTag();		// Set anyway - either it changes or it doesn't.
	return shouldSwitch;
}

void CC3Material::applyBlend()
{
	CC3OpenGLES11Engine* gles11Engine = CC3OpenGLES11Engine::engine();
	gles11Engine->getServerCapabilities()->getBlend()->setValue(!getIsOpaque());
	gles11Engine->getMaterials()->getBlend()->applySource(blendFunc.src, blendFunc.dst);
}

void CC3Material::applyColors()
{
	if (shouldUseLighting) 
	{
		CC3OpenGLES11Materials* gles11Materials = CC3OpenGLES11Engine::engine()->getMaterials();
		gles11Materials->getAmbientColor()->setValue(ambientColor);
		gles11Materials->getDiffuseColor()->setValue(diffuseColor);
		gles11Materials->getSpecularColor()->setValue(specularColor);
		gles11Materials->getEmissionColor()->setValue(emissionColor);
		gles11Materials->getShininess()->setValue(shininess);
	} 
	else 
	{
		CC3OpenGLES11Engine* gles11Engine = CC3OpenGLES11Engine::engine();
		gles11Engine->getServerCapabilities()->getLighting()->disable();
		//		gles11Engine.state.color.value = emissionColor;
		gles11Engine->getState()->getColor()->setValue(diffuseColor);
	}
}

void CC3Material::drawTexturesWithVisitor(CC3NodeDrawingVisitor* visitor)
{
	visitor->setTextureUnit(0);
	if (texture) 
	{
		texture->drawWithVisitor(visitor);
	}
	
	if (textureOverlays) 
	{
		CCObject* pObject = NULL;
		CCARRAY_FOREACH(textureOverlays, pObject)
		{
			CC3Texture* pTexture = (CC3Texture*)pObject;
			pTexture->drawWithVisitor(visitor);
		}
	}
	
	CC3Texture::unbindRemainingFrom(visitor->getTextureUnit());
	visitor->setTextureUnitCount(visitor->getTextureUnit());
}

void CC3Material::unbind()
{
	CC3OpenGLES11Engine::engine()->getServerCapabilities()->getBlend()->disable();
	resetSwitching();
	CC3Texture::unbind();
}

void CC3Material::populateFrom(CC3Material* another)
{
	CC3Identifiable::populateFrom(another);
	
	ambientColor = another->getAmbientColor();
	diffuseColor = another->getDiffuseColor();
	specularColor = another->getSpecularColor();
	emissionColor = another->getEmissionColor();
	shininess = another->getShininess();
	blendFunc = another->getBlendFunc();
	shouldUseLighting = another->getShouldUseLighting();
	isOpaque = another->getIsOpaque();
	
	texture->release();
	texture = another->getTexture();
	// todo
	texture->retain();//copy();			// retained
	
	// Remove any existing overlays and add the overlays from the other material.
	textureOverlays->removeAllObjects();
	
	cocos2d::CCArray* otherOTs = another->getTextureOverlays();
	CCObject* pObject = NULL;
	CCARRAY_FOREACH(otherOTs, pObject)
	{
		CC3Texture* ot = (CC3Texture*)pObject;
		//ot->copy();
		addTexture(ot);
	}
}

static GLuint lastAssignedMaterialTag = 0;

void CC3Material::resetTagAllocation()
{
	lastAssignedMaterialTag = 0;
}

GLuint CC3Material::getNextTag()
{
	return ++lastAssignedMaterialTag;
}

//@interface CC3Identifiable (TemplateMethods)
//-(void) populateFrom: (CC3Identifiable*) another;
//@end
//
//@interface CC3Material (TemplateMethods)
//-(void) applyColors;
//-(void) applyBlend;
//-(void) drawTexturesWithVisitor: (CC3NodeDrawingVisitor*) visitor;
//-(void) checkIsOpaque;
//-(BOOL) switchingMaterial;
//@end
//
//@implementation CC3Material
//
//@synthesize ambientColor, diffuseColor, specularColor, emissionColor, shininess;
//@synthesize texture, shouldUseLighting, isOpaque;
//
//-(void) dealloc {
//	[texture release];
//	[textureOverlays release];
//	[super dealloc];
//}
//
//-(void) setAmbientColor: (ccColor4F) aColor {
//	ambientColor = aColor;
//	[self checkIsOpaque];
//}
//
//-(void) setDiffuseColor: (ccColor4F) aColor {
//	diffuseColor = aColor;
//	[self checkIsOpaque];
//}
//
//-(void) setSpecularColor: (ccColor4F) aColor {
//	specularColor = aColor;
//	[self checkIsOpaque];
//}
//
//-(void) setEmissionColor: (ccColor4F) aColor {
//	emissionColor = aColor;
//	[self checkIsOpaque];
//}
//
//-(void) setShininess: (GLfloat) aValue {
//	shininess = CLAMP(aValue, 0.0, kCC3MaximumMaterialShininess);		// clamp to allowed range
//}
//
//-(GLenum) sourceBlend {
//	return blendFunc.src;
//}
//
//-(void) setSourceBlend: (GLenum) aBlend {
//	blendFunc.src = aBlend;
//	[self checkIsOpaque];
//}
//
//-(GLenum) destinationBlend {
//	return blendFunc.dst;
//}
//
//-(void) setDestinationBlend: (GLenum) aBlend {
//	blendFunc.dst = aBlend;
//	[self checkIsOpaque];
//}
//
//-(void) setIsOpaque: (BOOL) opaque {
//	if (opaque) {
//		// If we're forcing full opacity, set no alpha blending
//		blendFunc.src = GL_ONE;
//		blendFunc.dst = GL_ZERO;
//	} else {
//		// Enable alpha blending. Set destination blend to (1-SRC_ALPHA).
//		// Set source blend to SRC_ALPHA unless texture has pre-multiplied alpha AND
//		// material is at full opacity. If material is not at full opacity, we must
//		// enable source alpha blending even if texture has pre-multiplied alpha.
//		BOOL texHasPreMultAlpha = texture && texture.hasPremultipliedAlpha;
//		blendFunc.src = (texHasPreMultAlpha && self.opacity == 255) ? GL_ONE : GL_SRC_ALPHA;
//		blendFunc.dst = GL_ONE_MINUS_SRC_ALPHA;
//	}
//	[self checkIsOpaque];
//}
//
//
//#pragma mark CCRGBAProtocol & CCBlendProtocol support
//
//// Return diffuse color
//-(ccColor3B) color {
//	return CCC3BFromCCC4F(diffuseColor);
//}
//
//// Set both diffuse and ambient colors, retaining the alpha of each
//-(void) setColor: (ccColor3B) color {
//	GLfloat rf = CCColorFloatFromByte(color.r);
//	GLfloat gf = CCColorFloatFromByte(color.g);
//	GLfloat bf = CCColorFloatFromByte(color.b);
//	
//	ambientColor.r = rf;
//	ambientColor.g = gf;
//	ambientColor.b = bf;
//	
//	diffuseColor.r = rf;
//	diffuseColor.g = gf;
//	diffuseColor.b = bf;
//}
//
//// Return diffuse alpha
//-(GLubyte) opacity {
//	return CCColorByteFromFloat(diffuseColor.a);
//}
//
///**
// * Set opacity of all colors, retaining the colors of each.
// * If the opacity is less than full, make sure that the isOpaque property
// * is set appropriately. This is a convenience that ensures that a previously
// * opaque node can be faded without having to turn isOpaque off separately.
// */
//-(void) setOpacity: (GLubyte) opacity {
//	GLfloat af = CCColorFloatFromByte(opacity);
//	ambientColor.a = af;
//	diffuseColor.a = af;
//	specularColor.a = af;
//	emissionColor.a = af;
//
//	// As a convenience, if we're trying to reduce opacity, make sure the isOpaque
//	// flag is compatible with that. We do NOT force it the other way, because the
//	// texture may contain translucency, even if the material colors are fully opaque.
//	if (opacity < 255) {
//		self.isOpaque = NO;
//	}
//}
//
//-(ccBlendFunc) blendFunc {
//	return blendFunc;
//}
//
//-(void) setBlendFunc: (ccBlendFunc) aBlendFunc {
//	blendFunc = aBlendFunc;
//	[self checkIsOpaque];
//}
//
///**
// * Check if this material is opaque, and set the isOpaque flag accordingly.
// *
// * This method is called whenever any of the relevant properties are changed.
// *
// * This implementation is the minimal test of whether destination completely replaces
// * source, by checking that sourceBlend is GL_ONE and destinationBlend is GL_ZERO.
// * Subclasses may choose to implement tests for more sophisticated combinations.
// */
//-(void) checkIsOpaque {
//	isOpaque = (blendFunc.src == GL_ONE && blendFunc.dst == GL_ZERO);
//}
//
//static ccBlendFunc defaultBlendFunc = {GL_ONE, GL_ZERO};
//
//+(ccBlendFunc) defaultBlendFunc {
//	return defaultBlendFunc;
//}
//
//+(void) setDefaultBlendFunc: (ccBlendFunc) aBlendFunc {
//	defaultBlendFunc = aBlendFunc;
//}
//
//
//#pragma mark Textures
//
//-(GLuint) textureCount {
//	return (textureOverlays ? textureOverlays.count : 0) + (texture ? 1 : 0);
//}
//
//-(BOOL) hasBumpMap {
//	// Check the first texture.
//	if (texture && texture.isBumpMap) {
//		return YES;
//	}
//
//	// Then check in the overlays array
//	if (textureOverlays) {
//		for (CC3Texture* ot in textureOverlays) {
//			if (ot.isBumpMap) {
//				return YES;
//			}
//		}
//	}
//	return NO;
//}
//
//-(CC3Vector) lightDirection {
//	// Check the first texture.
//	if (texture && texture.isBumpMap) {
//		return texture.lightDirection;
//	}
//	
//	// Then check in the overlays array
//	if (textureOverlays) {
//		for (CC3Texture* ot in textureOverlays) {
//			if (ot.isBumpMap) {
//				return ot.lightDirection;
//			}
//		}
//	}
//	return kCC3VectorZero;
//}
//
//-(void) setLightDirection: (CC3Vector) aDirection {
//	// Set the first texture.
//	texture.lightDirection = aDirection;
//	
//	// Then check in the overlays array
//	if (textureOverlays) {
//		for (CC3Texture* ot in textureOverlays) {
//			ot.lightDirection = aDirection;
//		}
//	}
//}
//
//// If the texture property has not been set yet, set it. Otherwise add as an overlay.
//-(void) addTexture: (CC3Texture*) aTexture {
//	LogTrace(@"Adding %@ to %@", aTexture, self);
//	if (aTexture) {
//		if (!texture) {
//			self.texture = aTexture;
//		} else {
//			if(!textureOverlays) {
//				textureOverlays = [[NSMutableArray array] retain];
//			}
//			GLint maxTexUnits = [CC3OpenGLES11Engine engine].platform.maxTextureUnits.value;
//			if (self.textureCount < maxTexUnits) {
//				[textureOverlays addObject: aTexture];
//			} else {
//				LogInfo(@"Attempt to add texture %@ to %@ ignored because platform supports only %i texture units.",
//						aTexture, self, maxTexUnits);
//			}
//		}
//	}
//}
//
//// If it's the texture property, clear it, otherwise remove the overlay.
//-(void) removeTexture: (CC3Texture*) aTexture {
//	LogTrace(@"Removing %@ from %@", aTexture, self);
//	if (texture == aTexture) {
//		self.texture = nil;
//	} else {
//		if (textureOverlays && aTexture) {
//			[textureOverlays removeObjectIdenticalTo: aTexture];
//			if (textureOverlays.count == 0) {
//				[textureOverlays release];
//				textureOverlays = nil;
//			}
//		}
//	}
//}
//
//-(void) removeAllTextures {
//	// Remove the first texture
//	[self removeTexture: texture];
//
//	// Remove the overlay textures
//	if (textureOverlays) {
//		NSArray* myOTs = [textureOverlays copyAutoreleased];
//		for (CC3Texture* ot in myOTs) {
//			[self removeTexture: ot];
//		}
//	}
//}
//
//-(CC3Texture*) textureForTextureUnit: (GLuint) texUnit {
//	// If first texture unit, return texture property, otherwise retrieve from overlay array
//	if (texUnit == 0) {
//		return texture;
//	} else {
//		return [textureOverlays objectAtIndex: (texUnit - 1)];
//	}
//}
//
//-(void) setTexture: (CC3Texture*) aTexture forTextureUnit: (GLuint) texUnit {
//	NSAssert(aTexture, @"Overlay texture cannot be nil");
//	if (texUnit == 0) {
//		self.texture = aTexture;
//	} else if (texUnit < self.textureCount) {
//		[textureOverlays replaceObjectAtIndex: (texUnit - 1) withObject: aTexture];
//	} else {
//		[self addTexture: aTexture];
//	}
//}
//
//-(CC3Texture*) getTextureNamed: (NSString*) aName {
//	NSString* tcName;
//	
//	// First check if the first texture is the one
//	if (texture) {
//		tcName = texture.name;
//		if ([tcName isEqual: aName] || (!tcName && !aName)) {		// Name equal or both nil.
//			return texture;
//		}
//	}
//	// Then look for it in the overlays array
//	if (textureOverlays) {
//		for (CC3Texture* ot in textureOverlays) {
//			tcName = ot.name;
//			if ([tcName isEqual: aName] || (!tcName && !aName)) {		// Name equal or both nil.
//				return ot;
//			}
//		}
//	}
//	return nil;
//}
//
//#pragma mark Allocation and initialization
//
//-(id) initWithTag: (GLuint) aTag withName: (NSString*) aName {
//	if ( (self = [super initWithTag: aTag withName: aName]) ) {
//		texture = nil;
//		textureOverlays = nil;
//		ambientColor = kCC3DefaultMaterialColorAmbient;
//		diffuseColor = kCC3DefaultMaterialColorDiffuse;
//		specularColor = kCC3DefaultMaterialColorSpecular;
//		emissionColor = kCC3DefaultMaterialColorEmission;
//		shininess = kCC3DefaultMaterialShininess;
//		blendFunc = [[self class] defaultBlendFunc];
//		shouldUseLighting = YES;
//		[self checkIsOpaque];
//	}
//	return self;
//}
//
//+(id) material {
//	return [[[self alloc] init] autorelease];
//}
//
//+(id) materialWithTag: (GLuint) aTag {
//	return [[[self alloc] initWithTag: aTag] autorelease];
//}
//
//+(id) materialWithName: (NSString*) aName {
//	return [[[self alloc] initWithName: aName] autorelease];
//}
//
//+(id) materialWithTag: (GLuint) aTag withName: (NSString*) aName {
//	return [[[self alloc] initWithTag: aTag withName: aName] autorelease];
//}
//
//+(id) shiny {
//	CC3Material* mat = [self material];
//	if (mat) {
//		mat.specularColor = kCCC4FWhite;
//		mat.shininess = 75.0f;
//	}
//	return mat;
//}
//
//+(id) shinyWhite {
//	CC3Material* mat = [self shiny];
//	if (mat) {
//		mat.diffuseColor = kCCC4FWhite;
//	}
//	return mat;
//}
//
//// Protected properties for copying
//-(NSArray*) textureOverlays { return textureOverlays; }
//
//// Template method that populates this instance from the specified other instance.
//// This method is invoked automatically during object copying via the copyWithZone: method.
//-(void) populateFrom: (CC3Material*) another {
//	[super populateFrom: another];
//
//	ambientColor = another.ambientColor;
//	diffuseColor = another.diffuseColor;
//	specularColor = another.specularColor;
//	emissionColor = another.emissionColor;
//	shininess = another.shininess;
//	blendFunc = another.blendFunc;
//	shouldUseLighting = another.shouldUseLighting;
//	isOpaque = another.isOpaque;
//	
//	[texture release];
//	texture = [another.texture copy];			// retained
//	
//	// Remove any existing overlays and add the overlays from the other material.
//	[textureOverlays removeAllObjects];
//	NSArray* otherOTs = another.textureOverlays;
//	if (otherOTs) {
//		for (CC3Texture* ot in otherOTs) {
//			[self addTexture: [ot copyAutoreleased]];	// retained by collection
//		}
//	}
//
//}
//
//-(NSString*) fullDescription {
//	return [NSString stringWithFormat: @"%@, ambient: %@, diffuse: %@, specular: %@, emission: %@, shininess: %.2f, blend: (%@, %@), with %u textures",
//			[super fullDescription], NSStringFromCCC4F(ambientColor),
//			NSStringFromCCC4F(diffuseColor), NSStringFromCCC4F(specularColor),
//			NSStringFromCCC4F(emissionColor), shininess,
//			NSStringFromGLEnum(blendFunc.src), NSStringFromGLEnum(blendFunc.dst),
//			self.textureCount];
//}
//
//
//#pragma mark Tag allocation
//
//// Class variable tracking the most recent tag value assigned for CC3Materials.
//// This class variable is automatically incremented whenever the method nextTag is called.
//static GLuint lastAssignedMaterialTag;
//
//-(GLuint) nextTag {
//	return ++lastAssignedMaterialTag;
//}
//
//+(void) resetTagAllocation {
//	lastAssignedMaterialTag = 0;
//}
//
//
//#pragma mark Drawing
//
//-(void) drawWithVisitor: (CC3NodeDrawingVisitor*) visitor {
//	if ([self switchingMaterial]) {
//		[self applyBlend];
//		[self applyColors];
//		[self drawTexturesWithVisitor: visitor];
//	} else {
//		LogTrace(@"Reusing currently bound %@", self);
//	}
//}
//
///**
// * Enables or disables blending in the GL engine, depending on the whether or not this
// * instance is opaque or not, and applies the sourceBlend and destinationBlend properties.
// */
//-(void) applyBlend {
//	CC3OpenGLES11Engine* gles11Engine = [CC3OpenGLES11Engine engine];
//	gles11Engine.serverCapabilities.blend.value = !self.isOpaque;
//	[gles11Engine.materials.blend applySource: blendFunc.src andDestination: blendFunc.dst];
//}
//
///**
// * If the shouldUseLighting property is YES, applies the color and shininess properties to
// * the GL engine, otherwise turns lighting off and applies emission color as a flat color.
// */
//-(void) applyColors {
//	if (shouldUseLighting) {
//		CC3OpenGLES11Materials* gles11Materials = [CC3OpenGLES11Engine engine].materials;
//		gles11Materials.ambientColor.value = ambientColor;
//		gles11Materials.diffuseColor.value = diffuseColor;
//		gles11Materials.specularColor.value = specularColor;
//		gles11Materials.emissionColor.value = emissionColor;
//		gles11Materials.shininess.value = shininess;
//	} else {
//		CC3OpenGLES11Engine* gles11Engine = [CC3OpenGLES11Engine engine];
//		[gles11Engine.serverCapabilities.lighting disable];
////		gles11Engine.state.color.value = emissionColor;
//		gles11Engine.state.color.value = diffuseColor;
//	}
//}
//
///**
// * Draw the texture property and the texture overlays using separate GL texture units
// * The visitor keeps track of which texture unit is being processed, with each texture
// * incrementing the texture unit index as it draws.
// */
//-(void) drawTexturesWithVisitor: (CC3NodeDrawingVisitor*) visitor {
//	visitor.textureUnit = 0;
//	if (texture) {
//		[texture drawWithVisitor: visitor];
//	}
//	if (textureOverlays) {
//		for (CC3Texture* ot in textureOverlays) {
//			[ot drawWithVisitor: visitor];
//		}
//	}
//	[CC3Texture	unbindRemainingFrom: visitor.textureUnit];
//	visitor.textureUnitCount = visitor.textureUnit;
//}
//
//-(void) unbind {
//	[[self class] unbind];
//}
//
//+(void) unbind {
//	[[CC3OpenGLES11Engine engine].serverCapabilities.blend disable];
//	[self resetSwitching];
//	[CC3Texture unbind];
//}
//
//
//#pragma mark Material context switching
//
//// The tag of the material that was most recently drawn to the GL engine.
//// The GL engine is only updated when a material with a different tag is presented.
//// This allows for optimization by ordering the drawing of objects so that objects with the
//// same material are drawn together, to minimize context switching within the GL engine.
//static GLuint currentMaterialTag = 0;
//
//+(void) resetSwitching {
//	currentMaterialTag = 0;
//}
//
//-(BOOL) switchingMaterial {
//	BOOL shouldSwitch = currentMaterialTag != tag;
//	currentMaterialTag = tag;		// Set anyway - either it changes or it doesn't.
//	return shouldSwitch;
//}
//
//@end
